
# I M P O R T S:
# The imports. Note: I prefer importing this way (forcing an explicit dot
# operation) instead of using such things as "from Tkinter import *" & whatnot.
# I think it leads to better understanding, readability, and modularity.
from random import randint, gauss
from OpenGL.GL import *
from polygon import *

#import copy
from sys import exit, argv, path
import os


# My modules -taw

from taw_3DMath import limVec, subVec, scale, translate, calcMagnitude
from taw_time import Timer
from taw_3DMath import limVec, subVec, scale, translate, calcMagnitude
from taw_time import Timer



# C O N S T A N T S:
# Dimensions of the boids world -- X,Y,Z.
# Put all of this in the class sometime:
WORLD_DIM = [100, 100, 100]

# The boid types:
BIRDS	= 0	# birdlike (lines) -- coolest but slowest
CIRCLES	= 1	# faster
DASHS	= 2	# bee-like -- fastest
SQUARES	= 3	# NOT IMPLEMENTED YET

#DEFAULT_BOID_TYPE = DASHS
#DEFAULT_BOID_TYPE = CIRCLES
DEFAULT_BOID_TYPE = BIRDS

# NEIGHBOR_THRESH -> closest to each other that they feel comfortable.
# VELOCITY_LIM    -> Max velocity of a boid.
# Rule of thumb: NEIGHBOR_THRESH needs to be <= in comparison to VELOCITY_LIM.
#                I am not completely sure why, but, if not, flocking pattern
#                is not natural.
NEIGHBOR_THRESH	= 1 # -- toying with: int(WORLD_DIM[0]/10)
VELOCITY_LIM	= 1 # -- and int(NEIGHBOR_THRESH)

# Current: roughly 8 to 50 pixels in width (Make sure divisible by 2):
BOID_MAX_SIZE	= 40
BOID_MIN_SIZE	= 8


# Python needs these as standard. They aid in readability:
TRUE  = 1
FALSE = 0


class BoidListObj:
	"""
	This class defines a list of boids, what those boids are and the actions
	available to that boid/list.
	
	The static public variables are:
		boidType:	0, 1, 2, 3 (bird-like, circle, dash, square)
		s:		The class semaphore object.
		canvas:		The canvas that the boids are draw onto.
	
	The public methods are:
		__init__(self, n=None)
		DrawAll(self, initialYN=None)
		ScatterLogic(self)
		MoveAll(self)
		Rule1(self, b)
		Rule2(self, b)
		Rule3(self, b)
		LimitVelocity(self, b)
		BoundPosition(self, b)
		ShuffleList(self, l)
		
	Each member of the list is a boid with these attributes:
		[pt, vel, objNum]:
		  A pt:		[x, y, z] --- i.e., a position.
		  A vel:	[x, y, z] --- i.e., a vector.
		  An objNum:	...	  --- a number generated by Tk. Each object is
						  assigned a unique value to track on the
						  canvas.
		
	
	Created: 20000607	-taw: Todd Warner <taw@pobox.com>
	Updated: 20010326	-taw
	"""
	# These are static to all instances:
	boidType=	DASHS # -- someday get CircleToggleObj() working.

	def __init__(self, n=None):
		"""
		Method of class BoidListObj.
		
		This method initializes the object:
			o Time to scatter/tighten flock is set.
			o The timer is started.
			o Rules are turned on or off.
			o Boid type is chosen.
			o n boids are created and placed randomly appended to the list.
			o The boids are initially drawn.
		"""
		if not n: n = 0
		
		self.old_d = vector([1,0,0])
		
		self.theList	= []
		
	# Timer used with tScatter/tTighten.
	# NOTE: from taw_time: Timer()
		self.timer = Timer()	# Probably should implement this as num.
					# of steps as opposed to real system
					# time.

		self.waitTimer = Timer() # Used for sleepTimer here and there.

	# Occasionally the flock is scattered, and then regrouped. These two
	# variables store the values used to compute how much time, in seconds,
	# to do each.
	# tScatter: [time to run based on the other two parameters,
	#            mean time (used for normal randomness) of performance,
	#	     standard deviation]
	# tTighten: [ ... ditto ... ]
		self.tScatter = [ 3.0, 3.0,  1.0]
		self.tTighten = [10.0, 10.0, 5.0]
		
	# Here you can turn on(1) and off(-1) the rules.
	# I use 1 and -1 instead of 1 and 0 'cuz it is nice to toggle using
	# just a [(previous value) * (-1)] switch.
		self.r1Tog = 1
		self.r2Tog = 1 # Not implemented yet.
		self.r3Tog = 1 # Not implemented yet.
		
		#self.s.acquire()			# SEMAPHORE!!!!!
		# STUB: alternate the type of boid here.
	#self.s.release()			# SEMAPHORE!!!!!

	# Force the type of boid:
	# 	BIRDS or   0: bird-like (and slow)
	# 	CIRCLES or 1: circles (faster)
	# 	DASHS or   2: dashs (fastest)
	# 	SQUARES or 3: squares (not implemented yet)
	#       or DEFAULT_BOID_TYPE
		self.boidType = DEFAULT_BOID_TYPE

		for i in xrange(n):
			# NOTE: from random: randint, gauss
			pt = [randint(0, WORLD_DIM[0]),
				  randint(0, WORLD_DIM[1]),
				  randint(0, WORLD_DIM[2])]
			vel    = [gauss(0.0, VELOCITY_LIM/2),	# Initial velocity.
			  gauss(0.0, VELOCITY_LIM/2),
			  gauss(0.0, VELOCITY_LIM/2)]
			self.objNum = 0		# Object number (for Tkinter - we will save
				# a unique object number later on when drawing.

			self.theList.append([pt, vel, self.objNum])	# Append new boid on
							# the list.
			

		self.DrawAll(TRUE)	# Do the initial draw.
	# ----- def BoidListObj.__init__(self, n=none)



	def DrawAll(self, initialYN=None):
		"""
		Method of class BoidListObj.
	
		This method will draw all boids on the list to the canvas.
	
		PRE:  theList, and canvas are initialized.
			  initialYN == TRUE if drawing for the first time.
		POST: All boids on the list are drawn to the canvas.
		"""

		# I put a bit of a throttle in here so that 10 birds looks somewhat
		# similar to 20 birds as far as speed goes.
		self.waitTimer.startTimer()
	
		# If I shuffle the list, I get more natural flight patterns, BUT,
		# it is definitely a performance hit.
		self.theList  = self.ShuffleList(self.theList)	# **theList shuffle(opt)
	
		i = 0
		for each in self.theList:
				# NOTE: from random: randint
			fact = (BOID_MAX_SIZE/2 - BOID_MIN_SIZE) * \
			   each[0][2]/WORLD_DIM[2]+BOID_MIN_SIZE/2
			if fact < 0:
				fact = 1
			#    self.s.acquire()				# SEMAPHORE!!!!!

			if 1:	# dots / dashs -- speedy!
			 #   self.s.release()			# SEMAPHORE!!!!!
					x1 = (each[0][0] - fact - WORLD_DIM[0]/2) / 40.0
					y1 = (each[0][1] - fact - WORLD_DIM[1]/2) / 40.0
					z1 = (each[0][2] - fact - WORLD_DIM[2]/2) / 40.0
					d_new = vector([x1,y1,z1])
					d = d_new - self.old_d
					self.old_d = d_new
					n = d.cross(vector([0,1,0]))
					a = (d * vector([0,1,0]))
					if abs(a) > EPSILON:
						a = math.acos(a)
					glPushMatrix()
					
					glTranslatef(x1,y1,z1)
					glRotatef(a,n[0],n[1],n[2])
					
					
					
					glScalef(0.1,0.1,0.1)
					if i == self.objNum:
						glCallList(2)
					else:
						glCallList(1)
					glPopMatrix()
				
			i=i+1
				#	glBegin(GL_POINTS)
				#	glVertex3f(x1,y1,0.0)
				#	glVertex3f(x2,y2,0.0)
				#	glEnd()
			  #      self.s.acquire()			# SEMAPHORE!!!!!
	

	# ----- def BoidListObj.DrawAll(self, initialYN = FALSE)

	def ScatterLogic(self):
		"""
		Method of class BoidListObj.
	
		This method simply toggles whether Rule1 (boids head to center of
		of group) is on or off (1 or -1 -- not 1 or 0).
	
		PRE:  self.timer has been initialized.
			  self.tScatter[X,X,X] & self.tTighten[X,X,X] have been initialized.
		POST: The timer is checked. If time, then self.r1Tog is toggled.
		RET:  The toggle value.
		"""
		toggle = self.r1Tog

		if self.tScatter[0] > 0 and self.tTighten[0] > 0:
			# NOTE: from random: gauss
			# Ok... we are meant to change things, so...
			if toggle == -1:					    # scattering
				if self.timer.checkTimer() == -1: # i.e. it's been reset.
					while self.tScatter[0] <= 0:
						self.waitTimer.sleepTimer( 0.05 )
						self.tScatter[0] = gauss(self.tScatter[1],
									 self.tScatter[2])
					self.timer.startTimer()
				elif self.timer.checkTimer() >= self.tScatter[0]:
					toggle = toggle * -1
					print "It's safe now.\tTightening the flock..."
					self.timer.resetTimer()
			elif toggle == 1:					    # tightening
				if self.timer.checkTimer() == -1: # i.e. it's been reset.
					while self.tTighten[0] <= 0.0:
						self.waitTimer.sleepTimer( 0.05 )
						self.tTighten[0] = gauss(self.tTighten[1],
										self.tTighten[2])
					self.timer.startTimer()
				elif self.timer.checkTimer() >= self.tTighten[0]:
					toggle = toggle * -1
					print "Predator shows up!\tScattering the flock..."
					self.timer.resetTimer()
		return toggle
	# ---- def BoidListObj.ScatterLogic(self)



	def MoveAll(self):
		"""
		Method of class BoidListObj.
	
		This method moves all of the boids based on the 3 main rules and 
		various other factors.  Basically, all of the influencing vectors are
		added together and used as an offset to the original velocity vector.
		Finally the velocity is limited to something sensible.
	
		PRE:  self.theList and boids are intialized.
		POST: o Rule 1 toggles on (1) or off (-1) dependent on timer.
			  o Each boid given a new velocity vector and position.
		"""
		self.r1Tog = self.ScatterLogic()

		for each in self.theList:
		# vCntBias (Rule1):	head towards center of the flock.
		# vBckOff  (Rule2):	back off from crowding your neighbor.
		# vAvVBias (Rule3):	try to fly the same speed as the others.
		# vBound:		head back if near a wall.

			# NOTE: from taw_3DMath: scale, translate
			vCntBias = scale(self.Rule1(each), float(self.r1Tog))
			vBckOff  = scale(self.Rule3(each), float(self.r3Tog))
			vAvVBias = scale(self.Rule2(each), float(self.r2Tog))
			vBound   = self.BoundPosition(each)

			each[1] = translate(each[1], vCntBias)
			each[1] = translate(each[1], vBckOff)
			each[1] = translate(each[1], vAvVBias)
			each[1] = translate(each[1], vBound)

			each[1] = self.LimitVelocity(each)	# Don't fly too fast!

			each[0] = translate(each[1], each[0])	# add offset.
	# ----- def BoidListObj.MoveAll(self)



	def Rule1(self, b): # RULE #1:  Shoot for the percieved center of the flock.
		"""
		Method of class BoidListObj.
	
		This method will average all of the positions of all of the boids not
		including the current boid. Then subtract this value from the position
		of the current boid to get the perceived center of the flock. Finally,
		a step-size is figured (a velocity towards the center). This is done
		with an arbitrary value for now.
	
		PRE: theList and boids are initialized.
			 b: the current boid.
		RET: The velocity offset to head towards the center of the flock.
		"""

		# NOTE: from taw_3DMath: scale, translate, subVec

		pc = [0.0, 0.0, 0.0]
		for each in self.theList:
			if b != each:
				pc = translate(pc, each[0])

		fact = float(len(self.theList)) - 1.0
		fact = 1.0/fact
		
		pc = scale(pc, fact)
		pc = subVec(pc, b[0])
		fact    = 1.0/100.0 # Proportion
		pc = scale(pc, fact)
		return pc
	# ----- def BoidListObj.Rule1(self, b)



	def Rule2(self, b): # RULE #2:  Don't get too close to your neighbor.
		"""
		Method of class BoidListObj.
	
		This method will determine if we are too close to anyone in particular.
		If we are then attempt to head in the exact opposite direction.
	
		PRE: self.theList and boids are initialized.
			 b: the current boid.
		RET: If too close, a vector in the opposite direction, or a zerovector.
		"""
		c = [0.0, 0.0, 0.0]

		# NOTE: from taw_3DMath: subVec, calcMagnitude
		
		for each in self.theList:
			if b != each:
				diffPos = subVec(b[0], each[0])
				dist    = calcMagnitude(diffPos)
				
				if dist < NEIGHBOR_THRESH:
					c = subVec(c, diffPos)
		return c
	# ----- def BoidListObj.Rule2 (self, b)



	def Rule3(self, b): # RULE #3:  Try to match velocities:
		"""
		Method of class BoidListObj.
	
		This method has the current boid (b) attempt to match the velocity of
		the average of the rest of the flock.
	
		PRE: self.theList and boids are initialized.
			 b: the current boid.
		RET: The perceived velocity of the flock scaled to an arbitrary
			 step-size (1/8th in this case).
		"""
		pv = [0.0, 0.0, 0.0]

		# NOTE: from taw_3DMath: scale, translate, subVec
		
		for each in self.theList:
			if b != each:
				pv = translate(pv, each[1])
		fact = float(len(self.theList)) - 1.0
		fact = 1.0/fact
		pv = scale(pv, fact)			# velocities are averaged.
		
		fact = 1.0/8.0
		pv = subVec(pv, b[1])			# The difference is found
						# between the current and ave.

		pv = scale(pv, fact)			# The velocity used is 1/8th
						# of that factor.
		return pv
	# ----- def BoidListObj.Rule3(self, b)



	def LimitVelocity(self, b):
		"""
		Method of class BoidListObj.
	
			This (commented out region) is the method done in his pseudocode.
			The one used in practice is from his actual code. I had the problem
			of boids getting in a straight line with this original block of code.
	
		PRE: self.theList and boids initialized.
			 b: the current boid.
		RET: A more limited velocity if moving a bit too fast.
		"""
	##mag = taw_3DMath.calcMagnitude(b[1])
	##if mag > VELOCITY_LIM:
	##    mag = VELOCITY_LIM/mag
	##    return taw_3DMath.scale(b[1], mag)
	##return b[1]
			

		# His method used in his program - Probably used for performance.
		# NOTE: from taw_3DMath: limVec
		return limVec(b[1], VELOCITY_LIM)
	# ----- def BoidListObj.LimitVelocity(self, b)



	def BoundPosition(self, b):
		"""
		Method of class BoidListObj.
	
		This function will get a boid to turn a bit upon hitting one of the
		"walls" of the world's dimensions.
	
		PRE: A boids current velocity vector.
		RET: A vector to influence the boid to fly in a new direction.
		"""
		offset = -5.0	# Make nonzero of want the boids to begin turn at pt
			# before/after one of the walls.
		xMin = 0.0 - offset
		xMax = WORLD_DIM[0]  + offset
		yMin = 0.0 - offset
		yMax = WORLD_DIM[1]  + offset
		zMin = 0.0 - offset
		zMax = WORLD_DIM[2]  + offset
		
		v = [0.0, 0.0, 0.0]
		
		#factor = 50 - velocityLim	# I am not sure why I did this.
		factor = 0
		#if factor < 0:
		#    factor = 5
		if b[0][0] < xMin - factor:
			v[0] = 10.0
		elif b[0][0] > xMax + factor:
			v[0] = -10.0
			
		if b[0][1] < yMin - factor:
			v[1] = 10.0
		elif b[0][1] > yMax + factor:
			v[1] = -10.0
			
		if b[0][2] < zMin - factor:
			v[2] = 10.0
		elif b[0][2] > zMax + factor:
			v[2] = -10.0

		return v
	# ----- def BoidListObj.BoundPosition(self, b)



	def ShuffleList(self, l):
		"""
		Method of class BoidListObj.
	
		This function will randomly shuffle any list. I used this to simulate
		picking (without replacement) any boid from the "bag" of boids.
	
		PRE:  Any list (the list of boids).
		POST: The list is randomly reordered.
		RET:  The new list.
		"""

	# NOTE: from random: randint

		l2 = []
		while l != []:
			i = randint(0, len(l)-1)
			l2.append(l.pop(i))
		l = l2
		return l
	# ----- def BoidListObj.ShuffleList(self, l1)
	# ------------------------------------------------------- class BoidListObj
	# -------------------------------------------------------------------------


class pyBoids:

	def __init__(self,nr):
		
		self.waitTimer = Timer()
		self.waitTimer.sleepTimer(1)				# Wait a sec.
		self.howMany = nr
		self.l = BoidListObj(self.howMany)
		
	def update(self):
		self.l.MoveAll()
		
	def draw(self):
		self.l.DrawAll()

# ----------------------------------------------------------------------------
if __name__ == '__main__':
# ----------------------------------------------------------------------------
	# This is the main "function". This block only runs if used as an
	# executable.

	# NOTE: from threading: Thread
	#       from sys:       exit, argv


	tjohej = pyBoids(10)
	tjohej.update()
	print "Simulation ended!"
